# 'Friendly Flow' is a Server Framework for Python 2.5 and up
# Copyright (C) Xaba Software http://www.xaba.nl/
#
# This file is part of 'Friendly Flow'
#
# 'Friendly Flow' is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 'Friendly Flow' is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with 'Friendly Flow'; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

from unittest import TestCase
from socket import socket as python_socket, SOL_SOCKET, SO_REUSEADDR, error as SocketError
from select import select
from friendlyflow import socket, read, write, RETURN, LineReader, flatten

class SocketTest(TestCase):
    
    def setUp(self):
        self.port = 8999
        self.listening = python_socket()
        self.listening.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.listening.bind(('127.0.0.1', self.port))
        self.listening.listen(0)

    def connect(self):
        sock = socket()
        sock.connect(('127.0.0.1', self.port))
        otherSide, address = self.listening.accept()
        #note: otherSide is a "simple" python socket.socket
        return sock, otherSide
    
    def assertDone(self, generator):
        try:
            generator.next()
            self.fail()
        except StopIteration:
            pass

    def tearDown(self):
        self.listening.close()
    
    def testUnderlyingSockets(self):
        """Environment test codifies my understanding of python socket.socket"""
        sending = python_socket()
        sending.connect(('127.0.0.1', self.port))
        newConnection, address = self.listening.accept()
        self.assertTrue(newConnection)

    def testSocketIsDelegator(self):
        mockSocket = MockSocket()
        sock = socket(mockSocket)
        sock.bind('an address')
        self.assertEquals('an address', mockSocket.bindAddress)
    
    def testAccept(self):
        port = 8567
        sock = socket()
        sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        sock.bind(('127.0.0.1', port))
        sock.listen(0)
        
        generator = sock.accept()
        self.assertEquals(read(sock._sock.fileno()), generator.next())
        
        otherSide = python_socket()
        otherSide.connect(('127.0.0.1', port))
        
        token = generator.next()
        self.assertTrue(isinstance(token, RETURN))
        newConnection, address = token.value
        
        self.assertTrue(newConnection)

    def testConnect(self):
        self.connect()
        self.assertTrue('trivial, but the above does not fail')

    def testRead(self):
        sock, otherSide = self.connect()
        
        generator = sock.read(20)
        token = generator.next()
        self.assertEquals(read(sock._sock.fileno()), token)
        
        otherSide.sendall("0123456789")
        
        select([token.descriptor], [], [], None)
        token = generator.next()
        
        self.assertTrue(isinstance(token, RETURN))
        value = token.value
        self.assertEquals("0123456789"[:len(value)], value)
        
        self.assertDone(generator)

    def testReadAll(self):
        sock, otherSide = self.connect()
        
        data = "0123456789abcdefghij"
        
        generator = sock.readAll(20)
        token = generator.next()
        self.assertEquals(read(sock._sock.fileno()), token)
        
        for x in data:
            otherSide.sendall(x)
            select([token.descriptor], [], [], None)
            token = generator.next()
            
            if x != data[-1]:
                self.assertEquals(read(sock._sock.fileno()), token)
            else:
                self.assertTrue(isinstance(token, RETURN))
                self.assertEquals(data, token.value)

    def testWrite(self):
        sock, otherSide = self.connect()
        aLot = 1000000
        
        generator = sock.write("0" * aLot)
        token = generator.next()
        self.assertEquals(write(sock._sock.fileno()), token)
        
        select([], [token.descriptor], [], None)
        token = generator.next()
        self.assertTrue(isinstance(token, RETURN))
        
        #admittedly the following test may fail on some systems.
        #esp. on Linux it is known that buffer size increases when big buffers are written
        self.assertTrue(0 < token.value < aLot)
        
        received = otherSide.recv(10)
        self.assertEquals('0000000000', received)

        self.assertDone(generator)

    def testWriteAll(self):
        sock, otherSide = self.connect()
        aLot = 1000000
        data = "0" * aLot
        
        otherSide.setblocking(0)
        
        generator = sock.writeAll(data)
        token = generator.next()
        self.assertEquals(write(sock._sock.fileno()), token)
        
        while isinstance(token, write):
            try:
                #read from the other side to empty buffers on write side
                otherSide.recv(aLot)
            except SocketError, e:
                RESOURCE_TEMPORARILY_UNAVAILABLE = 11
                assert e.args[0] == RESOURCE_TEMPORARILY_UNAVAILABLE
            
            #poll for writing
            ignored, wReady, ignored = select([], [token.descriptor], [], 0)
            if wReady:
                token = generator.next()

        self.assertTrue(isinstance(token, RETURN))
        self.assertEquals(aLot, token.value)
    
    def testReadLineNoNewLineInitially(self):
        sock, otherSide = self.connect()
        sock = LineReader(sock)
        
        generator = flatten(sock.readLine())
        
        token = generator.next()
        self.assertEquals(read(sock._sock.fileno()), token)
        otherSide.sendall("li")
        select([token.descriptor], [], [], None)
        
        token = generator.next()
        self.assertEquals(read(sock._sock.fileno()), token)
        otherSide.sendall("ne 0")
        select([token.descriptor], [], [], None)
        
        token = generator.next()
        self.assertEquals(read(sock._sock.fileno()), token)
        otherSide.sendall("\r\ntrailing")
        select([token.descriptor], [], [], None)
        
        self.expectReturn(generator, "line 0")
        
        generator = sock.readAll(len("trailing"))
        self.expectReturn(generator, "trailing")

    def testReadLineAllDataSentFirst(self):
        sock, otherSide = self.connect()
        sock = LineReader(sock)
        
        generator = flatten(sock.readLine())
        
        token = generator.next()
        self.assertEquals(read(sock._sock.fileno()), token)
        
        otherSide.sendall("line 0\r\nline 1\r\nthe rest containing more\r\nnewlines")
        select([token.descriptor], [], [], None)
        
        self.expectReturn(generator, "line 0")
        
        generator = sock.readLine()
        self.expectReturn(generator, "line 1")
        
        generator = sock.readAll(len("the rest containing more\r\nnewlines"))
        self.expectReturn(generator, "the rest containing more\r\nnewlines")

    def expectReturn(self, generator, value):
        token = generator.next()
        self.assertTrue(isinstance(token, RETURN))
        value = token.value
        self.assertEquals(value, token.value)
        self.assertDone(generator)

class MockSocket(object):
    
    def bind(self, address):
        self.bindAddress = address
        
    def setblocking(self, *args, **kwargs):
        pass

#connect is a non-blocking call... but not a generator. How to make this 
#generally understandable?

#test readline followed by read.

#readAll with closed socket
    
#if socket.connect contains anything that needs further resolving (www.xaba.nl instead of 123.123.123.123) this may block.

#read with closed socket.
